﻿Imports System.Text
Imports System.Reflection

Namespace Util

    ''' <summary>
    ''' コレクション操作用ユーティリティクラス。
    ''' </summary>
    ''' <history>[sonodar] 2011/11/10 新規作成</history>
    Public NotInheritable Class CollectionUtil

        ''' <summary>
        ''' デフォルトコンストラクタを不可視とすることで、<br/>
        ''' インスタンスの生成を禁止する。
        ''' </summary>
        Private Sub New()
        End Sub

        ''' <summary>
        ''' コレクションまたは配列が空かどうかを返す。
        ''' </summary>
        ''' <param name="Collection">コレクションまたは配列</param>
        ''' <remarks>Nothingまたは要素数0の場合にTrueを返す。</remarks>
        ''' <returns>説明参照</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function IsEmpty(ByVal Collection As IEnumerable) As Boolean

            '// 要素が存在したらFalse
            If (Collection IsNot Nothing AndAlso _
                                        Collection.GetEnumerator.MoveNext()) Then
                Collection.GetEnumerator.Reset()    '// カーソルを先頭に戻す
                Return False
            End If

            Return True

        End Function

        ''' <summary>
        ''' コレクションまたは配列のすべての要素が空かどうかを返す。
        ''' </summary>
        ''' <param name="Collection">コレクションまたは配列</param>
        ''' <returns>
        ''' すべての要素が空だった場合、True。<br/>
        ''' 1つでも空でない要素があった場合はFalse。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function IsEmptyAll(ByVal Collection As IEnumerable) As Boolean

            '// コレクション自体が空の場合はTrue
            If (CollectionUtil.IsEmpty(Collection)) Then
                Return True
            End If

            '// どれか１つでも空でない要素があったらFalse
            For Each item As Object In Collection
                If (Not CommonUtil.IsEmpty(item)) Then
                    Return False
                End If
            Next

            Return True

        End Function

        ''' <summary>
        ''' コレクションまたは配列を区切り文字で連結した文字列を返す。
        ''' </summary>
        ''' <param name="Collection">連結するコレクションまたは配列</param>
        ''' <param name="Delim">区切り文字 (省略した場合は空文字(""))</param>
        ''' <param name="PropertyName">Collectionの要素に含まれるプロパティ名 (省略した場合はコレクション要素の文字列表現)</param>
        ''' <returns>区切り文字で連結された文字列</returns>
        Public Overloads Shared Function Join(Of T)(ByVal Collection As IEnumerable(Of T), _
                                              Optional ByVal Delim As String = "", _
                                              Optional ByVal PropertyName As String = Nothing) As String
            '// 空の場合は空文字を返す
            If (CollectionUtil.IsEmpty(Collection)) Then
                Return String.Empty
            End If

            '// 戻り値を初期化
            Dim retStr As StringBuilder = New StringBuilder()

            '// コレクションの列挙を取得
            Dim e As IEnumerator(Of T) = Collection.GetEnumerator()

            Dim item As T           '// コレクションの要素
            Dim value As Object     '// 実際に連結する文字列

            e.MoveNext()            '// 先頭の要素にカーソルをあわせる
            item = e.Current        '// 先頭の要素を取得

            '// 先頭要素を文字列に変換
            If (item Is Nothing) Then
                '// 要素がNULLの場合は空文字
                value = String.Empty
            ElseIf (PropertyName IsNot Nothing) Then
                'プロパティ名が指定されていた場合は、要素のプロパティを設定
                value = ReflectionUtil.GetProperty(item, PropertyName)
            Else
                '// それ以外は要素を文字列として設定
                value = item.ToString()
            End If

            '// 先頭要素の文字列を追加
            retStr.Append(value)

            '// 要素がある限り繰り返す
            While (e.MoveNext())

                retStr.Append(Delim)    '// 先頭要素以降は最初にデリミタを追加
                item = e.Current        '// 現在の要素にカーソルをあわせる

                '// 先頭要素のときと同じ手順で要素を文字列に変換
                If (item Is Nothing) Then
                    value = String.Empty
                ElseIf (PropertyName IsNot Nothing) Then
                    value = ReflectionUtil.GetProperty(item, PropertyName)
                Else
                    value = item.ToString
                End If

                '// 文字列を追加
                retStr.Append(value)

            End While

            '// 生成した文字列を返す
            Return retStr.ToString

        End Function

        ''' <summary>
        ''' コレクションまたは配列を区切り文字で連結した文字列を返す。
        ''' </summary>
        ''' <param name="Collection">連結するコレクションまたは配列</param>
        ''' <param name="Delim">区切り文字 (省略した場合は空文字(""))</param>
        ''' <param name="PropertyName">Collectionの要素に含まれるプロパティ名 (省略した場合はコレクション要素の文字列表現)</param>
        ''' <returns>区切り文字で連結された文字列</returns>
        Public Overloads Shared Function Join(ByVal Collection As ICollection, _
                                              Optional ByVal Delim As String = "", _
                                              Optional ByVal PropertyName As String = Nothing) As String
            '// 空の場合は空文字を返す
            If (CollectionUtil.IsEmpty(Collection)) Then
                Return String.Empty
            End If

            '// 戻り値を初期化
            Dim retStr As StringBuilder = New StringBuilder()

            '// コレクションの列挙を取得
            Dim e As IEnumerator = Collection.GetEnumerator()

            Dim item As Object      '// コレクションの要素
            Dim value As Object     '// 実際に連結する文字列

            e.MoveNext()            '// 先頭の要素にカーソルをあわせる
            item = e.Current        '// 先頭の要素を取得

            '// 先頭要素を文字列に変換
            If (item Is Nothing) Then
                '// 要素がNULLの場合は空文字
                value = String.Empty
            ElseIf (PropertyName IsNot Nothing) Then
                'プロパティ名が指定されていた場合は、要素のプロパティを設定
                value = ReflectionUtil.GetProperty(item, PropertyName)
            Else
                '// それ以外は要素を文字列として設定
                value = item.ToString()
            End If

            '// 先頭要素の文字列を追加
            retStr.Append(value)

            '// 要素がある限り繰り返す
            Do While (e.MoveNext())

                retStr.Append(Delim)     '// 先頭要素以降は最初にデリミタを追加
                item = e.Current            '// 現在の要素にカーソルをあわせる

                '// 先頭要素のときと同じ手順で要素を文字列に変換
                If (item Is Nothing) Then
                    value = String.Empty
                ElseIf (PropertyName IsNot Nothing) Then
                    value = ReflectionUtil.GetProperty(item, PropertyName)
                Else
                    value = item.ToString()
                End If

                '// 文字列を追加
                retStr.Append(value)
            Loop

            '// 生成した文字列を返す
            Return retStr.ToString

        End Function

        'TODO ここまで精査完了

        ''' <summary>
        ''' リストの先頭に空行を追加して返す。
        ''' </summary>
        ''' <param name="list">行を追加するリスト</param>
        ''' <returns>先頭に空行が追加されたIList</returns>
        ''' <remarks>
        ''' すべての要素が同じ型で、かつ、デフォルトコンストラクタを持つ型の場合のみ<br/>
        ''' 空行を追加できる。
        ''' </remarks>
        Public Overloads Shared Function InsertBlankItem(ByVal list As IEnumerable) As IList

            Dim dataList As IList = New ArrayList()

            Dim item As Object
            If (list IsNot Nothing) Then
                For Each item In list
                    dataList.Add(item)
                Next
            End If
            If (CollectionUtil.IsEmpty(dataList)) Then
                Return dataList
            End If
            Dim itemType As Type = dataList.Item(0).GetType
            If (itemType.GetConstructor(Type.EmptyTypes) Is Nothing) Then
                Throw New System.Exception( _
                    "リスト要素の" + itemType.FullName + _
                    "にはデフォルトコンストラクタがないため空行を追加できません。")
            End If
            item = Activator.CreateInstance(itemType)
            dataList.Insert(0, item)
            Return dataList
        End Function

        ''' <summary>
        ''' リストの先頭に空行を追加して返す。
        ''' </summary>
        ''' <param name="list">行を追加するリスト</param>
        ''' <returns>先頭に空行が追加されたIList</returns>
        ''' <remarks>
        ''' 要素の型がデフォルトコンストラクタを持つ型の場合のみ<br/>
        ''' 空行を追加できる。
        ''' </remarks>
        Public Overloads Shared Function AddFirstBlankRow(Of T)(ByVal list As IEnumerable(Of T)) As IList(Of T)
            Dim dataList As IList(Of T) = New List(Of T)
            Dim item As T
            If (list IsNot Nothing) Then
                For Each item In list
                    dataList.Add(item)
                Next
            End If
            If (CollectionUtil.IsEmpty(dataList)) Then
                Return dataList
            End If
            Dim itemType As Type = GetType(T)
            If (itemType.GetConstructor(Type.EmptyTypes) Is Nothing) Then
                Throw New System.Exception( _
                    "リスト要素の" + itemType.FullName + _
                    "にはデフォルトコンストラクタがないため空行を追加できません。")
            End If
            item = DirectCast(Activator.CreateInstance(itemType), T)
            ReflectionUtil.SetPropertyByType(item, GetType(String), String.Empty)
            dataList.Insert(0, item)
            Return dataList
        End Function

        ''' <summary>
        ''' 指定した長さの配列にすべて同じ値を設定して返す。
        ''' </summary>
        ''' <typeparam name="T">要素の型</typeparam>
        ''' <param name="obj">設定する値</param>
        ''' <param name="length">
        ''' 配列の要素数。<br/>
        ''' 0を指定した場合は空の配列が返る。<br/>
        ''' 0未満の値を指定した場合はNothingが返る。
        ''' </param>
        ''' <returns>値が設定された配列</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function FillArray(Of T)(ByVal obj As T, ByVal length As Integer) As T()
            If (length < 0) Then
                Return Nothing
            End If
            Dim arr(length - 1) As T
            For i As Integer = 0 To length - 1
                arr(i) = obj
            Next
            Return arr
        End Function

        ''' <summary>
        ''' 複数のリストを1つのリストに結合して返す。<br/>
        ''' 引数で渡した順番に結合する。
        ''' </summary>
        ''' <typeparam name="T">リスト要素の総称型</typeparam>
        ''' <param name="lists">結合するリスト</param>
        ''' <returns>結合されたリスト</returns>
        Public Shared Function Concat(Of T)(ByVal ParamArray lists() As IList(Of T)) As IList(Of T)

            If (IsEmpty(lists)) Then Return Nothing

            Dim listClass As Type = GetType(List(Of T))

            For Each listItem As IList(Of T) In lists
                If (listItem IsNot Nothing) Then
                    listClass = listItem.GetType
                    Exit For
                End If
            Next

            Dim list As IList(Of T) = DirectCast(Activator.CreateInstance(listClass), IList(Of T))

            For Each listItem As IList(Of T) In lists
                For Each element As T In listItem
                    list.Add(element)
                Next
            Next

            Return list
        End Function

    End Class

End Namespace